/**
 * 温室的控制系统示例
 *
 * @author fireway
 * @since  2019年 03月 10日 星期日 08:24:10 CST
 */
package concurrency;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class GreenhouseScheduler {
    private String mThermostat = "Day";

    private volatile boolean mLight = false;

    private volatile boolean mWater = false;

    private ScheduledThreadPoolExecutor mScheduler = new ScheduledThreadPoolExecutor(10);

    private List<DataPoint> mDatas = Collections.synchronizedList(new ArrayList<DataPoint>());

    private Calendar mLastTime = Calendar.getInstance();

    private Random mRand = new Random(47);

    private int mTemperatureDirection = +1;

    private float mLastTemperature = 15.0f;

    private int mHumidityDirection = +1;

    private float mLastHumidity = 80.0f;

    /**
     * 关于“构造代码块和构造函数有什么区别”，请参考：
     * https://zhidao.baidu.com/question/1927325908074180187.html
     */
    {
        // Adjust date to the half hour
        mLastTime.set(Calendar.MINUTE, 30);
        mLastTime.set(Calendar.SECOND, 00);
    }

    class Terminate implements Runnable {
        @Override
        public void run() {
            System.out.println("Terminating");
            mScheduler.shutdownNow();
            // Must start a separate task to do this job,
            // since the scheduler has been shut down:
            new Thread() {
                public void run() {
                    for (DataPoint d : mDatas) {
                        System.out.println(d);
                    }
                }
            } .start();
        }
    }

    class Bell implements Runnable {
        @Override
        public void run() {
            System.out.println("Bing!");
        }
    }

    class ThermostatDay implements Runnable {
        @Override
        public void run() {
            // Put hardware control code here.
            System.out.println("Thermostat to day setting");
            setThermostat("Day");
        }
    }

    class ThermostatNight implements Runnable {
        @Override
        public void run() {
            // Put hardware control code here.
            System.out.println("Thermostat to night setting");
            setThermostat("Night");
        }
    }

    class LightOn implements Runnable {
        @Override
        public void run() {
            // Put hardware control code here to
            // physically turn on the light.
            System.out.println("Turning on lights");
            mLight = true;
        }
    }

    class LightOff implements Runnable {
        @Override
        public void run() {
            // Put hardware control code here to
            // physically turn off the light.
            System.out.println("Turning off lights");
            mLight = false;
        }
    }

    class WaterOn implements Runnable {
        @Override
        public void run() {
            // Put hardware control code here.
            System.out.println("Turning greenhouse water on");
            mWater = true;
        }
    }

    class WaterOff implements Runnable {
        @Override
        public void run() {
            // Put hardware control code here.
            System.out.println("Turning greenhouse water off");
            mWater = false;
        }
    }

    class CollectData implements Runnable {
        @Override
        public void run() {
            System.out.println("Collecting data");
            synchronized(GreenhouseScheduler.this) {
                // Pretend the interval is longer than it is:
                mLastTime.set(Calendar.MINUTE, mLastTime.get(Calendar.MINUTE) + 30);
                // One in 5 chances of reversing the direction:
                if(mRand.nextInt(5) == 4) {
                    mTemperatureDirection = -mTemperatureDirection;
                }
                // Store previous value:
                mLastTemperature = mTemperatureDirection + mTemperatureDirection * (1.0f + mRand.nextFloat());

                if(mRand.nextInt(5) == 4) {
                    mHumidityDirection = -mHumidityDirection;
                }
                mLastHumidity = mLastHumidity + mHumidityDirection * mRand.nextFloat();
                // Calendar must be cloned, otherwise all
                // DataPoints hold references to the same lastTime.
                // For a basic object like Calendar, clone() is OK.
                mDatas.add(new DataPoint((Calendar)mLastTime.clone(), mLastTemperature, mLastHumidity));
            }
        }
    }

    static class DataPoint {
        final Calendar mTime;

        /**
         * 温度
         */
        final float mTemperature;

        /**
         * 湿度
         */
        final float mHumidity;

        public DataPoint(Calendar d, float temp, float hum) {
            mTime = d;
            mTemperature = temp;
            mHumidity = hum;
        }

        @Override
        public String toString() {
            return mTime.getTime()
                   + String.format(" temperature: %1$.1f, humidity: %2$.2f",
                                   mTemperature, mHumidity);
        }
    }

    public void schedule(Runnable event, long delay) {
        mScheduler.schedule(event, delay, TimeUnit.MILLISECONDS);
    }

    public void repeat(Runnable event, long initialDelay, long period) {
        mScheduler.scheduleAtFixedRate(event, initialDelay, period, TimeUnit.MILLISECONDS);
    }

    public synchronized void setThermostat(String value) {
        mThermostat = value;
    }

    public static void main(String[] args) {
        GreenhouseScheduler gh = new GreenhouseScheduler();
        gh.schedule(gh.new Terminate(), 5000);
        // Former "Restart" class not necessary:
        gh.repeat(gh.new Bell(), 0, 1000);
        gh.repeat(gh.new ThermostatNight(), 0, 2000);
        gh.repeat(gh.new LightOn(), 0, 200);
        gh.repeat(gh.new LightOff(), 0, 400);
        gh.repeat(gh.new WaterOn(), 0, 600);
        gh.repeat(gh.new WaterOff(), 0, 800);
        gh.repeat(gh.new ThermostatDay(), 0, 1400);
        gh.repeat(gh.new CollectData(), 500, 500);
    }
}
